home *** CD-ROM | disk | FTP | other *** search
- /*
- ** File: Switch Stack.c
- **
- ** Contains: A simple 68K example of a VBL written in C that runs on a
- ** private stack. This won't compile for PowerPC, so don't try it.
- ** This is a 68K-only thing.
- **
- ** Written by: Jim Luther (Based on the VBL code from the Technical Note
- ** "TB 35 - MultiFinder Miscellanea".)
- **
- ** Copyright: © 1993-1995 by Apple Computer, Inc., all rights reserved.
- **
- ** Change History (most recent first):
- **
- ** <8> 3 Feb 98 Quinn Expanded comment about holding the private stack.
- ** <7> 05/30/95 JML Added check for VM before using HoldMemory and UnholdMemory.
- ** <6> 02/18/95 JML Added HoldMemory and UnholdMemory calls to keep stack in
- ** physical memory.
- ** <5> 01/27/95 JML Added .r file for resources
- ** <4> 01/27/95 JML Got rid of the THINK C-isms. Now builds with THINK C, MPW C, and Metrowerks C
- ** <3> 11/02/93 JML Added reentrancy comment to VBL
- ** <2> 10/13/93 JML Minor cleanup
- ** <1> 10/08/93 JML First pass
- **
- */
-
- #include <Types.h>
- #include <Memory.h>
- #include <QuickDraw.h>
- #include <Fonts.h>
- #include <Windows.h>
- #include <Menus.h>
- #include <TextEdit.h>
- #include <Dialogs.h>
- #include <Events.h>
- #include <TextUtils.h>
- #include <Retrace.h>
- #include <LowMem.h>
- #include <Gestalt.h>
-
- /*----------------------------------------------------------------------------*/
-
- /*
- ** Define a struct to keep track of what we need to swap stacks.
- **
- ** WARNING: Do not change this structure. The assembly inlines
- ** SwitchtoPrivateStack and RestoreStack depend on the
- ** exact order of the fields in this record!
- */
- struct StackRec
- {
- Ptr ourStackBottom; /* saved stack bottom where we can find it */
- Ptr ourStackTop; /* saved stack top where we can find it */
- Ptr savedA7; /* place where VBL saves current A7 */
- Ptr savedStkLowPt; /* place where VBL saves StkLowPt */
- Ptr savedHiHeapMark; /* place where VBL saves HiHeapMark */
- };
- typedef struct StackRec StackRec;
- typedef StackRec *StackRecPtr;
-
- /*
- ** Define a struct to keep track of what we need in the VBL. Put theVBLTask
- ** into the struct first because its address will be passed to our VBL task
- ** in A0.
- */
- struct VBLRec
- {
- VBLTask theVBLTask; /* the VBL task itself */
- long VBLA5; /* saved CurrentA5 where we can find it */
- StackRec stackRecord;
- };
- typedef struct VBLRec VBLRec;
- typedef struct VBLRec *VBLRecPtr;
-
- /*----------------------------------------------------------------------------*/
-
- /*
- ** Constants used in sample
- */
-
- enum
- {
- kInterval = 6, /* VBL interval */
- rInfoDialog = 140, /* DLOG resource ID */
- rStatTextItem = 1, /* item number of counter field in dialog */
-
- kStackSize = 0x4000 /* 16K */
- };
-
- /*----------------------------------------------------------------------------*/
-
- /*
- ** Prototypes
- */
-
- pascal void DoVBL (VBLRecPtr recPtr);
- void StartVBL(void);
- void main(void);
-
- /*----------------------------------------------------------------------------*/
-
- /*
- ** A global which will be referenced from our VBL Task and the test program
- */
-
- long gCounter; /* Counter incremented each time our VBL gets called */
- long gVMOn = false; /* true if System 7 VM is turned on */
-
- /*----------------------------------------------------------------------------*/
-
- /*
- ** GetVBLRec returns the address of the VBLRec associated with our VBL task.
- ** This works because on entry into the VBL task, A0 points to the theVBLTask
- ** field in the VBLRec record, which is the first field in the record and that
- ** is the address we return. Note that this method works whether the VBLRec
- ** is allocated globally, in the heap (as long as the record is locked in
- ** memory) or if it is allocated on the stack as is the case in this example.
- ** In the latter case this is OK as long as the procedure which installed the
- ** task does not exit while the task is running. This trick allows us to get
- ** to the saved A5, but it could also be used to get to anything we wanted to
- ** store in the record.
- */
- extern VBLRecPtr GetVBLRec(void)
- = 0x2008; /* MOVE.L A0,D0 */
-
- /*
- ** SwitchtoPrivateStack and RestoreStack are assembly language inlines
- ** that perform the stack switch using the StackRec passed as a parameter
- ** to SwitchtoPrivateStack.
- */
-
- #pragma parameter SwitchtoPrivateStack(__A0)
- extern void SwitchtoPrivateStack(StackRec *sr)
- = { /* ; A0 = the StackRecPtr parameter */
- 0x2178, 0x0BAE, 0x0010, /* MOVE.L HiHeapMark,StackRec.savedHiHeapMark(A0) ; save HiHeapMark */
- 0x2178, 0x0110, 0x000C, /* MOVE.L StkLowPt,StackRec.savedStkLowPt(A0) ; save StkLowPt */
- 0x214f, 0x0008, /* MOVE.L SP,StackRec.savedA7(A0) ; save current system stack */
- 0x42b8, 0x0110, /* CLR.L StkLowPt ; disable the stack sniffer */
- 0x21D0, 0x0BAE, /* MOVE.L StackRec.ourStackBottom(A0),HiHeapMark ; set HiHeapMark to bottom of our stack */
- 0x2e68, 0x0004, /* MOVEA.L StackRec.ourStackTop(A0),SP ; switch stacks */
- 0x2f08 /* MOVE.L A0,-(SP) ; save A0 (*sr) on top of private stack */
- };
-
- extern void RestoreStack(void)
- = {
- 0x205F, /* MOVE.L (SP)+,A0 ; restore A0 (*sr) from private stack */
- 0x2E68, 0x0008, /* MOVEA.L StackRec.savedA7(A0),A7 ; restore system stack */
- 0x21E8, 0x0010, 0x0BAE, /* MOVE.L StackRec.savedHiHeapMark(A0),HiHeapMark ; restore HiHeapMark */
- 0x21E8, 0x000C, 0x0110 /* MOVE.L StackRec.savedStkLowPt(A0),StkLowPt ; restore the sniffer */
- };
-
- /*----------------------------------------------------------------------------*/
-
- /*
- ** DoVBL is called only by StartVBL()
- **
- ** WARNING: It MUST be declared "pascal" so the function will remove the
- ** parameters itself!
- */
- pascal void DoVBL (VBLRecPtr recPtr)
- {
- gCounter++; /* Show we can set a global */
- recPtr->theVBLTask.vblCount = kInterval; /* Set ourselves to run again */
- }
-
- /*----------------------------------------------------------------------------*/
-
- /*
- ** This is the actual VBL task code. It uses GetVBLRec to get our VBL record
- ** and properly set up A5. Having done that, switches to a private stack and
- ** then calls DoVBL to increment a global counter and sets itself to run again.
- ** Because of the vagaries of C optimization, it calls a separate routine to
- ** actually access global variables. See "OV 10 - Setting and Restoring A5"
- ** for the reasons for this, as well as for a description of SetA5.
- ** I can switch to my private stack without checking to make sure I haven't
- ** already done so because VBLs don't have to worry about begin reentrant.
- */
- void StartVBL ()
- {
- long curA5;
- VBLRecPtr recPtr;
-
- recPtr = GetVBLRec(); /* First get our record */
- curA5 = SetA5(recPtr->VBLA5); /* Get the saved A5 */
- /* Now we can access globals */
-
- /* Switch to private stack */
- SwitchtoPrivateStack(&recPtr->stackRecord);
-
- /* Now we're running on our private stack */
-
- /*
- ** Make sure any functions called at this point use pascal calling conventions
- ** so the functions clean up the stack before returning!
- */
- DoVBL(recPtr); /* Call another routine to do actual work */
-
- /* Switch back to original stack */
- RestoreStack();
-
- (void) SetA5(curA5); /* Restore old A5 */
- }
-
- /*----------------------------------------------------------------------------*/
-
- /*
- ** Create a dialog just to demonstrate that the global variable
- ** is being updated by the VBL Task. Before installing the VBL, we store
- ** our A5 in the actual VBL Task record, using SetCurrentA5 described in
- ** OV 10 - Setting and Restoring A5. We also store the location of our
- ** private stack which the VBL code will run under. We'll run the VBL,
- ** showing the counter being incremented, until the mouse button is clicked.
- ** Then we remove the VBL Task, close the dialog, and remove the mouse down
- ** events to prevent the application from being inadvertently switched
- ** by MultiFinder.
- */
- void main (void)
- {
- VBLRec theVBLRec;
- DialogPtr infoDPtr;
- DialogRecord infoDStorage;
- Str255 numStr = "\pTest";
- OSErr theErr;
- Handle theItemHandle;
- short theItemType;
- Rect theRect;
- long lastCount = 0;
- long response;
-
- InitGraf(&qd.thePort);
- InitFonts();
- InitWindows();
- InitMenus();
- TEInit();
- InitDialogs(NULL);
- InitCursor();
- MaxApplZone();
-
- /* check for VM */
- if ( Gestalt(gestaltVMAttr, &response) == noErr )
- {
- gVMOn = ((response & (1L << gestaltVMPresent)) != 0);
- }
-
- /* Store the current value of A5 in the VBLA5 field. */
- theVBLRec.VBLA5 = SetCurrentA5 ();
-
- /* Allocate memory for our private stack and store it's location in ourStackBottom */
- theVBLRec.stackRecord.ourStackBottom = NewPtrSys(kStackSize);
- if ( theVBLRec.stackRecord.ourStackBottom != NULL )
- {
- if ( gVMOn )
- {
- /* Make sure the stack is held in physical memory. Otherwise we could
- end up running interrupt time code on a pageable stack. This would
- be bad, because a page fault allocating stack space would attempt
- to create a exception frame on the same interrupt stack, which would
- cause a double bus fault. See DTS Technote 1094 for details.
- */
- theErr = HoldMemory(theVBLRec.stackRecord.ourStackBottom, kStackSize);
- }
- else
- {
- theErr = noErr;
- }
- if ( theErr == noErr )
- {
- /* Store location of our stack's top in ourStackTop */
- theVBLRec.stackRecord.ourStackTop =
- (Ptr)((unsigned long)theVBLRec.stackRecord.ourStackBottom + kStackSize);
-
- gCounter = 0; /* Initialize our global counter */
-
- /* Put up the dialog */
- infoDPtr = GetNewDialog (rInfoDialog, (Ptr) &infoDStorage, (WindowPtr) -1);
- DrawDialog (infoDPtr);
- GetDItem (infoDPtr, rStatTextItem, &theItemType, &theItemHandle, &theRect);
-
- /* Set the address of our routine */
- theVBLRec.theVBLTask.vblAddr = NewVBLProc(StartVBL);
- theVBLRec.theVBLTask.vblCount = kInterval; /* Frequency of task, in ticks */
- theVBLRec.theVBLTask.qType = vType; /* qElement is a VBL task */
- theVBLRec.theVBLTask.vblPhase = 0;
-
- /* Now install the VBL task */
- theErr = VInstall((QElemPtr)&theVBLRec.theVBLTask);
-
- /* Display the counter until the mouse button is pushed */
- if ( theErr == noErr )
- {
- do
- {
- if (gCounter != lastCount)
- {
- lastCount = gCounter;
- NumToString(gCounter, numStr);
- SetIText(theItemHandle, numStr);
- }
- } while ( !Button () );
- theErr = VRemove((QElemPtr)&theVBLRec.theVBLTask); /* Remove it when done */
- }
-
- /* Finish up */
- CloseDialog (infoDPtr); /* Get rid of our dialog */
- FlushEvents (mDownMask, 0); /* Flush all mouse down events */
-
- if ( gVMOn )
- {
- /* Release the hold on our stack */
- theErr = UnholdMemory(theVBLRec.stackRecord.ourStackBottom, kStackSize);
- }
- else
- {
- theErr = noErr;
- }
- }
-
- /* Dispose of the private stack */
- DisposePtr(theVBLRec.stackRecord.ourStackBottom);
- }
- }
-